package com.wsoft.interceptor;


import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.wsoft.core.exception.Asserts;
import com.wsoft.core.utils.LoginUserUtil;
import com.wsoft.core.utils.SqlAuthDisableHolder;
import com.wsoft.enums.DataPermissionEnum;
import com.wsoft.system.entity.SysDataPermEntity;
import com.wsoft.system.entity.SysOrganizationEntity;
import com.wsoft.system.service.CommonMyBatisService;
import com.wsoft.system.service.ISysDataPermService;
import com.wsoft.system.service.ISysOrganizationService;
import com.wsoft.system.service.ISysRolePermRelationService;
import com.wsoft.utils.ResourceUtil;
import com.wsoft.vo.LoginUserVO;
import lombok.extern.log4j.Log4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;

import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;



/**
 * @Author Zhang gukai
 * @Date 2024/10/16 10:48
 */
@Log4j
public class DataPermInterceptor extends DataPermissionInterceptor {
    private static final ThreadLocal<Integer> CALL_FLAG = new ThreadLocal<>();

    @Lazy
    @Autowired
    private ISysRolePermRelationService rolePermRelationService;

    @Lazy
    @Autowired
    private CommonMyBatisService commonMyBatisService;

    @Lazy
    @Autowired
    private ISysDataPermService dataPermService;

    @Lazy
    @Resource
    private ISysOrganizationService organizationService;


    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        if (!SqlAuthDisableHolder.needSqlAuth()) {
            return;
        }
        String firstSql = boundSql.getSql();
        Field field = null;

        //记录第一次LocalPage
        Integer i = ThreadContext.getThread();
        Page page = null;
        if(i == null || i == 0){
            page = PageHelper.getLocalPage() == null?null:(Page)PageHelper.getLocalPage().clone();
        }

        try {
            Select statement = (Select) CCJSqlParserUtil.parse(boundSql.getSql());

            //获取用户角色
            LoginUserVO loginUserVO = null;
            try {
                loginUserVO = LoginUserUtil.getUserInfo().getLoginUserVO();
                if (loginUserVO == null) {
                    return;
                }
            } catch (Exception e) {
                return;
            }

            //获取当前执行sql涉及的表名
            List<String> mainTables = getMainTable(statement);

            //权限过滤系统表
            if (containsTsOrSysTable(mainTables)) {
                return;
            }

            //information_schema表不做权限
            if(checkInformationSchemaTable(mainTables)){
                return;
            }
            field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            //
            String authTable = getFirstTableOnField(mainTables);
            if(authTable!=null) {
                try {
                    //根据用户角色获取数据权限
                    ThreadContext.setThread(1);
                    List<SysDataPermEntity> dataPermList = rolePermRelationService.getDataPermListByMemberId(loginUserVO.getId());
                    Function<SysDataPermEntity, String> fieldExtractor = SysDataPermEntity::getTableName;
                    Map<Boolean, List<SysDataPermEntity>> splitMap = dataPermList.stream()
                            .collect(Collectors.partitioningBy(obj -> fieldExtractor.apply(obj) != null && !fieldExtractor.apply(obj).isEmpty()));

                    //指定权限
                    List<SysDataPermEntity> nonEmptyList = splitMap.get(true);
                    //默认权限
                    List<SysDataPermEntity> emptyOrNullList = splitMap.get(false);
                    ThreadContext.setThread(0);
                    PageHelper.clearPage();
                    PageHelper.setLocalPage(page);
                    List<SysDataPermEntity> result = new ArrayList<>();
                    //匹配当前执行表的数据权限
                    for (String e : mainTables) {
                        List<SysDataPermEntity> collect = nonEmptyList.stream().filter(item->{
                            return item.getTableName().contains(e);
                        }).collect(Collectors.toList());
                        result.addAll(collect);
                    }
                    //存在数据权限 修改sql
                    if (!result.isEmpty()||!emptyOrNullList.isEmpty()){
                        String newSql = addReTableCondition(boundSql.getSql(), loginUserVO, result, emptyOrNullList);
                        //通过反射修改sql语句
                        field.set(boundSql, newSql);
                    }
                } catch (Exception e) {
                    System.out.println("数据权限控制失败");
                    field.set(boundSql, firstSql);
                }
            }
        } catch (JSQLParserException | NoSuchFieldException | IllegalAccessException e) {
            if(StringUtils.isNotEmpty(firstSql)&& ObjectUtils.isNotEmpty(field)){
                try {
                    field.set(boundSql, firstSql);
                } catch (IllegalAccessException illegalAccessException) {
                    illegalAccessException.printStackTrace();
                }
            }
            Asserts.fail("数据异常");
            e.printStackTrace();
        }
    }

    /**
     * 获取tables的表名
     *
     * @param statement
     * @return
     */
    private List<String> getMainTable(Select statement) {
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        return tablesNamesFinder.getTableList((Statement) statement);
    }

    /**
     * 检查是否为系统表
     * @param tables
     * @return
     */
    private boolean containsTsOrSysTable(List<String> tables) {
        return tables.stream()
                .anyMatch(tableName -> tableName.startsWith("wf_from_column_link")
                        || tableName.contains("relation")
                );
    }

        /**
         * 验证是否包含InformationSchema表
         * @param tables
         * @return
         */
    private boolean checkInformationSchemaTable(List<String> tables) {
        if(CollectionUtil.isEmpty(tables)){
            return true;
        }
        for (String table : tables) {
            if(table!=null && table.toLowerCase().indexOf("information_schema")>=0){
                return true;
            }
        }
        return false;
    }
    /**
     * 在原有的sql中增加新的where条件
     */
    private void addWhereCondition(PlainSelect plainSelect, String condition) {
        try {
            final Expression expression = plainSelect.getWhere();
            final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition);
            if (Objects.isNull(expression)) {
                plainSelect.setWhere(envCondition);
            } else {
                AndExpression andExpression = new AndExpression(expression, envCondition);
                plainSelect.setWhere(andExpression);
            }
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }
    }
    private String getCondition (String tableName, LoginUserVO loginUserVO, List<SysDataPermEntity> dto) {
        StringBuilder condition = new StringBuilder();
        dto.forEach(e -> {
            if (checkAddCondition(tableName,e.getRuleType())) {
                String sql = getNewSqlByPerm(e, loginUserVO, tableName);
                condition.append(sql).append(" or ");
            }
            });

        int index = condition.lastIndexOf("or");
        String result = "";
        if (index != -1) {
             result = condition.substring(0, index);
        }
        return "".equals(result)?"":"("+result+")";
    }
    private Boolean checkAddCondition(String tableName,Integer ruleType){
        Set<Integer> orgRuleTypes = new HashSet<>();
        orgRuleTypes.add(DataPermissionEnum.DEPT_SELF.getCode());
        orgRuleTypes.add(DataPermissionEnum.DEPT_SELF_CHILDREN.getCode());
        orgRuleTypes.add(DataPermissionEnum.ORG_PARENT.getCode());
        orgRuleTypes.add(DataPermissionEnum.ORG_PARENT_CHILDREN.getCode());
        orgRuleTypes.add(DataPermissionEnum.ORG_FREE.getCode());
        orgRuleTypes.add(DataPermissionEnum.ORG_FREE_CHILDREN.getCode());
        Set<Integer> memberRuleTypes = new HashSet<>();
        memberRuleTypes.add(DataPermissionEnum.SELF.getCode());
        memberRuleTypes.add(DataPermissionEnum.MEMBER_FREE.getCode());
        if (commonMyBatisService.getFieldExists(tableName,"org_id")>0 &&orgRuleTypes.contains(ruleType)){
            return true;
        }
        if (commonMyBatisService.getFieldExists(tableName,"create_by")>0 &&memberRuleTypes.contains(ruleType)){
            return true;
        }
        return false;
    }
    private String getNewSqlByPerm(SysDataPermEntity entity,LoginUserVO loginUserVO,String tableName){
        DataPermissionEnum value = DataPermissionEnum.getByCode(entity.getRuleType());
        SysOrganizationEntity org = new SysOrganizationEntity();
        List<SysOrganizationEntity> orgList = new ArrayList<>();
        String authOrg = "";
        if (entity.getRuleType().equals(DataPermissionEnum.ORG_PARENT.getCode())
                || entity.getRuleType().equals(DataPermissionEnum.ORG_PARENT_CHILDREN.getCode())
                || entity.getRuleType().equals(DataPermissionEnum.DEPT_SELF_CHILDREN.getCode())
                ||entity.getRuleType().equals(DataPermissionEnum.ORG_FREE_CHILDREN.getCode()))
        {
             orgList = organizationService.listAllOrgWithCache();
             org = orgList.stream().filter(o -> o.getId().equals(loginUserVO.getCurOrgId())).findFirst().get();
            if (entity.getRuleType().equals(DataPermissionEnum.DEPT_SELF_CHILDREN.getCode())){
                 authOrg = org.getAuthCode();
             }
            else if (entity.getRuleType().equals(DataPermissionEnum.ORG_PARENT.getCode())){
                authOrg = getUpOrgByLevel("1", org.getAuthCode());
            }
            else if(entity.getRuleType().equals(DataPermissionEnum.ORG_FREE_CHILDREN.getCode())){
                authOrg = getOrgFreeChildrenCondition(entity.getRuleValue(), orgList);
            }
             else {
                 authOrg = getUpOrgByLevel(entity.getRuleValue(), org.getAuthCode());
             }
        }

        switch (value){
            case ALL:
                return "";
            case SELF:
                return tableName+".create_by = "+loginUserVO.getId();
            case DEPT_SELF:
                return tableName+".org_id = "+loginUserVO.getCurOrgId();
            case DEPT_SELF_CHILDREN:
                return "sys_organization.auth_code like '"+authOrg+"%'";
            case ORG_PARENT:
                return "sys_organization.auth_code = '"+ authOrg+"'";
            case ORG_PARENT_CHILDREN:
                return "sys_organization.auth_code like '"+ authOrg+"%'";
            case ORG_FREE:
                return tableName+".org_id in("+entity.getRuleValue()+")";
            case ORG_FREE_CHILDREN:
                return authOrg;
            case MEMBER_FREE:
                return tableName+".create_by in("+entity.getRuleValue()+")";
            default:
                return "";
        }
    }

    private String getOrgFreeChildrenCondition(String ruleValue,List<SysOrganizationEntity> orgList) {
        String[] split = ruleValue.split(",");
        StringBuilder sb = new StringBuilder();
        int totalItems = split.length;
        int currentIndex = 1;
        for (String s : split) {
            if (currentIndex == totalItems) {
                SysOrganizationEntity entity = orgList.stream().filter(o -> o.getId().equals(Long.parseLong(s))).findFirst().get();
                sb.append("sys_organization.auth_code like '"+entity.getAuthCode()+"%'") ;
            }
            else {
                SysOrganizationEntity entity = orgList.stream().filter(o -> o.getId().equals(Long.parseLong(s))).findFirst().get();
                sb.append("sys_organization.auth_code like '"+entity.getAuthCode()+"%'") ;
                sb.append(" or ");
                currentIndex++;
            }
        }
        return sb.toString();
    }
    private String getUpOrgByLevel(String level, String orgCode){
        int i = orgCode.length() - Integer.parseInt(level) * 3;
        if (i<=0){
            return orgCode.substring(0,3);
        }
        return orgCode.substring(0,i);
    }
    private String addReTableCondition(String sql, LoginUserVO loginUserVO, List<SysDataPermEntity> result, List<SysDataPermEntity> specificDataPerm) throws Exception {
        try {
            Select select = (Select) CCJSqlParserUtil.parse(sql);
            if (select instanceof  SetOperationList) {
                // 判断是否是union查询
                SetOperationList setOperationList = select.getSetOperationList();
                List<Select> selects = setOperationList.getSelects();
                for (int i = 0; i < selects.size(); i++) {
                    //遍历赋予权限
                    plainSelectPermissions((PlainSelect)selects.get(i),loginUserVO, result ,specificDataPerm);
                }
            } else {
                PlainSelect plainSelect = select.getPlainSelect();
                plainSelectPermissions(plainSelect, loginUserVO, result, specificDataPerm);
            }

            return select.toString();
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }
    }

    private void plainSelectPermissions(PlainSelect plainSelect, LoginUserVO loginUserVO, List<SysDataPermEntity> result, List<SysDataPermEntity> specificDataPerm) {

        //1、formItem是不是需要做数据权限
        Object obj = plainSelect.getFromItem();
        if (obj instanceof Select) {
            subSelectPermissions((Select)obj, loginUserVO , result, specificDataPerm);
        } else if (obj instanceof Table) {
            Table table = (Table) obj;
            List<SysDataPermEntity> dataPerm = getDataPerm(table.getName(), result, specificDataPerm);
            if (dataPerm.stream().anyMatch(e -> e.getRuleType().equals(DataPermissionEnum.ORG_PARENT.getCode())
                        || e.getRuleType().equals(DataPermissionEnum.ORG_PARENT_CHILDREN.getCode())
                        || e.getRuleType().equals(DataPermissionEnum.DEPT_SELF_CHILDREN.getCode())
                    || e.getRuleType().equals(DataPermissionEnum.ORG_FREE_CHILDREN.getCode()))) {
                if (commonMyBatisService.getFieldExists(table.getName(),"org_id")>0&&!"sys_organization".equals(table.getName())){
                    modifyTable(plainSelect, table);
                }
                }
            String condition = getCondition(table.getName(), loginUserVO, dataPerm);
            if (!condition.isEmpty()){
                addWhereCondition(plainSelect, condition);
            }
             }

        //2、join是否需要做权限控制
//        List<Join> listJoin = plainSelect.getJoins();
//        if (listJoin != null && listJoin.size() > 0) {
//            for (int i = 0; i < listJoin.size(); i++) {
//                Join join = listJoin.get(i);
//                Object joinObj = join.getRightItem();
//                if (joinObj instanceof Table) {
//                    Table joinTable = (Table) joinObj;
//                    List<SysDataPermEntity> dataPerm = getDataPerm(joinTable.getName(), result, specificDataPerm);
//                    if (dataPerm.stream().anyMatch(e -> e.getRuleType().equals(DataPermissionEnum.ORG_PARENT.getCode())
//                            || e.getRuleType().equals(DataPermissionEnum.ORG_PARENT_CHILDREN.getCode())
//                            || e.getRuleType().equals(DataPermissionEnum.DEPT_SELF_CHILDREN.getCode())
//                            || e.getRuleType().equals(DataPermissionEnum.ORG_FREE_CHILDREN.getCode()))) {
//                        if (commonMyBatisService.getFieldExists(joinTable.getName(), "org_id") > 0 && !"sys_organization".equals(joinTable.getName())) {
//                            modifyTable(plainSelect, joinTable);
//                        }
//                    }
//                    String condition = getCondition(joinTable.getName(), loginUserVO, dataPerm);
//                    if (!condition.isEmpty()){
//                        addWhereCondition(plainSelect, condition);
//                    }
//                }
//            }
//        }
    }



    private void subSelectPermissions(Select subselect, LoginUserVO loginUserVO, List<SysDataPermEntity> result, List<SysDataPermEntity> specificDataPerm) {
        if (subselect.getSetOperationList() != null) {
            SetOperationList setOperationList = subselect.getSetOperationList();
            List<Select> selects = setOperationList.getSelects();
            for (int i = 0; i < selects.size(); i++) {
                //遍历赋予权限
                plainSelectPermissions((PlainSelect)selects.get(i),loginUserVO, result ,specificDataPerm);
            }
        } else {
            PlainSelect plainSelect = subselect.getPlainSelect();
            //赋予权限
            plainSelectPermissions(plainSelect,loginUserVO, result ,specificDataPerm);
        }
    }

    /**
     * 根据表名获取数据权限
     */
    private List<SysDataPermEntity> getDataPerm(String tableName,List<SysDataPermEntity> result, List<SysDataPermEntity> specificDataPerm ) {
        if (!result.isEmpty() ){
             return result.stream().filter(e -> e.getTableName().contains(tableName)).collect(Collectors.toList());
        }
        if (result.isEmpty()&&specificDataPerm!=null){
            return specificDataPerm;
        }
        return null;
    }
    /**
     *根据权限修改sql
     */
    private void modifyTable(PlainSelect plainSelect, Table table) {
        String tableName = table.getName();


            Table organizationTable = new Table("sys_organization");
            Column mainTableColumn = new Column("org_id");
            mainTableColumn.setTable(new Table(tableName.trim()));
            Column orgTableColumn = new Column("id");
            orgTableColumn.setTable(organizationTable);

            EqualsTo equalsTo = new EqualsTo();
            equalsTo.setLeftExpression(mainTableColumn);
            equalsTo.setRightExpression(orgTableColumn);
            // 创建JOIN对象
            Join newJoin = new Join();
            newJoin.setLeft(true);
            newJoin.setRightItem(organizationTable);
            newJoin.addOnExpression(equalsTo);
            // 添加新的 JOIN 到原始 SELECT 语句
            plainSelect.addJoins(newJoin);
    }

    /**
     * 获取拥有字段的指定第一张表
     *
     * @return
     */
    private String getFirstTableOnField(List<String> tables) {
        if(CollectionUtil.isEmpty(tables)){
            return null;
        }
        for (String table : tables) {
            if(commonMyBatisService.getFieldExists(table, "org_id")>0
            || commonMyBatisService.getFieldExists(table, "create_by")>0){
                return table;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        String[] split = "1".split(",");
        StringBuilder sb = new StringBuilder();
        int totalItems = split.length;
        System.out.println(totalItems);
        int currentIndex = 1;
        for (String s : split) {
            if (currentIndex == totalItems) {
                sb.append("sys_organization.code like '"+split[currentIndex-1]+"%'") ;
            }
            else {
                sb.append("sys_organization.code like '"+split[currentIndex-1]+"%'") ;
                sb.append(" or ");
                currentIndex++;
            }
        }
        System.out.println(sb);
    }
}


